package state


import ."api"


type luaStack struct {

	slots	[]luaValue		// 存放值

	top		int				// 记录栈顶索引

	prev	*luaStack 	// 上一个函数调用栈, 让调用帧变成了链表节点

	closure	*closure	// 可以从中提取指令或者常量

	varargs	[]luaValue 	// 可变参数列表, 实现变成函数机制

	pc 		int			// 程序计数器

	// 为了支持注册表伪索引，需要从luaStack结构体添加State字段
	// 间接访问注册表
	state	*luaState

	// 为了能够在合适的时机(比如局部变量退出作用域时)把处于开放状态的Upvalue闭合
	// 需要记录所有暂时还处于开放状态的Upvalue
	// 我们把这些Upvalue记录在被捕获局部变量所在的栈帧里
	// 键是int类型，存放局部变量的寄存器索引
	// 值是Upvalue指针
	openuvs 	map[int]*upvalue
}


//创建指定容量的栈
func newLuaStack(size int, state *luaState) *luaStack {

	return &luaStack{
		slots:	make([]luaValue, size),

		top:	0,

		state:	state,
	}
}


// 检查栈的空闲空间是否还可以容纳(推入)至少n个值
// 如果不满足这个条件，则调用Go语言内置的append()函数进行扩容
func (self *luaStack) check(n int) {

	free := len(self.slots) - self.top

	for i := free; i < n; i++ {
		self.slots = append(self.slots, nil)
	}
}


// 将值推入栈顶，如果溢出，则暂时先调用内置的panic()函数终止程序
func (self *luaStack) push(val luaValue) {

	if self.top == len(self.slots) {
		panic("stack overflow!")
	}

	self.slots[self.top] = val
	self.top++
}


// 从栈顶弹出一个值，如果栈是空的，则调用panic()函数终止程序
func (self *luaStack) pop() luaValue {

	if self.top < 1 {
		panic("stack underflow")
	}

	self.top--
	val := self.slots[self.top]
	self.slots[self.top] = nil

	return val
}


// 把索引转成绝对索引（并没有考虑索引是否有效）
func (self *luaStack) absIndex(idx int) int {

	// 如果索引小于等于LUA_REGISTRYINDEX, 说明是伪索引，直接返回即可
	if idx <= LUA_REGISTRYINDEX {

		return idx

	}

	if idx >= 0 {
		return idx
	}

	return idx + self.top + 1
}


// 判断索引是否有效
func (self *luaStack) isValid(idx int) bool {

	// 如果索引小于注册表索引，说明是Upvalue伪索引
	// 把它转成真实索引(从0开始)
	// 然后看看它是否在有效范围之内
	if idx < LUA_REGISTRYINDEX { /* upvalues */

		uvIdx := LUA_REGISTRYINDEX - idx - 1
		c := self.closure

		return c != nil && uvIdx < len(c.upvals)

	}

	// 注册表伪索引属于有效索引，所以直接返回true
	if idx == LUA_REGISTRYINDEX {

		return true

	}

	absIdx := self.absIndex(idx)

	return absIdx > 0 && absIdx <= self.top
}


// 根据索引从栈里取值，如果索引无效则返回nil值
func (self *luaStack) get(idx int) luaValue {

	// 如果伪索引无效，直接返回nil
	// 否则返回Upvalue值
	if idx < LUA_REGISTRYINDEX {  // upvalues

		uvIdx := LUA_REGISTRYINDEX - idx - 1
		c := self.closure
		if c == nil || uvIdx >= len(c.upvals) {
			return nil
		}

		return *(c.upvals[uvIdx].val)

	}

	// 如果索引是注册表伪索引，直接修改返回注册表
	if idx == LUA_REGISTRYINDEX {

		return self.state.registry

	}

	if self.isValid(idx) == false {
		return nil
	}

	absIdx := self.absIndex(idx)

	return self.slots[absIdx - 1]
}


// 根据索引往栈里写入值，如果索引无效，则调用panic()函数终止程序
func (self *luaStack) set(idx int, val luaValue) {

	// 如果伪索引有效，就修改Upvalue值
	// 否则，直接返回
	if idx < LUA_REGISTRYINDEX {  /* upvalues */

		uvIdx := LUA_REGISTRYINDEX - idx - 1
		c := self.closure
		if c != nil && uvIdx < len(c.upvals) {
			*(c.upvals[uvIdx].val) = val
		}

		return

	}

	// 如果索引是注册表伪索引，直接修改注册表
	// 这里并没有检查传入的值是否真的是Lua表
	// 所以如果传入的是其他类型的值可能会导致注册表变成nil
	// 还好我们一般只是操作注册表就可以了
	// 很少需要直接把整个注册表替换掉
	if idx == LUA_REGISTRYINDEX {

		self.state.registry = val.(*luaTable)

		return

	}

	if self.isValid(idx) == false {
		panic("invalid index!")
	}

	absIdx := self.absIndex(idx)
	self.slots[absIdx-1] = val
}


// 翻转操作
func (self *luaStack) reverse(from, to int) {

	slots := self.slots

	for from < to {
		slots[from], slots[to] = slots[to], slots[from]
		from++
		to--
	}
} 


// 从栈顶一次性弹出多个值
func (self *luaStack) popN(n int) []luaValue {

	vals := make([]luaValue, n)

	// 栈顶值放在列表末尾
	for i := n - 1; i >= 0; i-- {

		vals[i] = self.pop()
	}

	return vals
}


// 往栈顶推入多个值(多退少补)
func (self *luaStack) pushN(vals []luaValue, n int) {

	nVals := len(vals)

	if n < 0 {
		n = nVals
	}

	for i := 0; i < n; i++ {
		if i < nVals {
			self.push(vals[i])
		} else {
			self.push(nil)
		}
	}
}